// HttpSocket.cpp: implementation of the HttpSocket class.
//
//////////////////////////////////////////////////////////////////////

#include "HttpSocket.h"

#include <iostream>
#include <string>

using namespace std;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
HttpSocket::HttpSocket(string const server, string const object, string const cookie, 
    string const referer, long nFrom, long nTo, int nServerType, string port, int timeOut)
:_status(HttpSocket::HTTP_SOCKET_UNINIT)
,_socket(NULL)
,_port(atoi(port.c_str()))
,_timeOut(timeOut)
{
    _formatRequestHeader(server, object, cookie, referer, nFrom, nTo, nServerType);
    _connect(server.c_str());
    _sendRequest();
}

HttpSocket::HttpSocket(string const url, string const cookie, string const referer, int timeOut)
:_status(HttpSocket::HTTP_SOCKET_UNINIT)
,_socket(NULL)
,_timeOut(timeOut)
{
    string service, server, object, port;
    HttpSocket::parseUrl(url, service, server, object, port);
    _formatRequestHeader(server, object, cookie, referer);
    _connect(server.c_str());
    _sendRequest();
}

HttpSocket::~HttpSocket()
{
    _closeSocket();
}

void HttpSocket::parseUrl(string const &url, string &service, string &server, string &object, string &port)
{
    cout << "parsing url: " << url << endl;

    size_t iService = url.find("://");
    size_t iServer = url.find("/", iService+3);
    size_t iObject = url.find(":", iService+3);
    service = url.substr(0, iService);
    server = url.substr(iService+3, iServer-iService-3);
    object = url.substr(iServer, iObject-iServer-1);
    if (string::npos == iObject)
    {
        port = "80";
    }
    else
    {
        port = url.substr(iObject+1, url.length()-iObject-1);
    }
}

size_t HttpSocket::_formatRequestHeader(string const &server, string const &object, 
                                     string const &cookie, string const &referer, long nFrom, 
                                     long nTo, int nServerType)
{
    _requestHeader.clear();

    // method, request path, version information
    _requestHeader.append("GET ");
    _requestHeader.append(object);
    _requestHeader.append(" HTTP/1.1");
    _requestHeader.append("\r\n");

    // host
    _requestHeader.append("Host: ");
    _requestHeader.append(server);
    _requestHeader.append("\r\n");

    if(referer.length() != 0)
    {
        _requestHeader.append("Referer: ");
        _requestHeader.append(referer);
        _requestHeader.append("\r\n");        
    }

    // accept data type
    _requestHeader.append("Accept: */*");
    _requestHeader.append("\r\n");

    _requestHeader.append("Accept-Language: en-us\r\n");
    _requestHeader.append("UA-CPU: x86\r\n");

    // browser type
    _requestHeader.append("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1;  EmbeddedWB 14.52; .NET CLR 2.0.50727; .NET CLR 2.0.50215)");
    _requestHeader.append("\r\n");

    // connection config
    _requestHeader.append("Connection: Keep-Alive");
    _requestHeader.append("\r\n");

    // cookie
    if(cookie.length() != 0)
    {
        _requestHeader.append("Cookie: ");
        _requestHeader.append(cookie);
        _requestHeader.append("\r\n");
    }

    // the postion of starting, the key of broken continue transform
    if(nFrom > 0)
    {
        _requestHeader.append("Range: bytes=");

        char szTemp[20];
        sprintf_s(szTemp, "%l", nFrom);
        _requestHeader.append(szTemp);
        _requestHeader.append("-");
        if(nTo > nFrom)
        {
            sprintf_s(szTemp, "%l", nTo);
            _requestHeader.append(szTemp);
        }
        _requestHeader.append("\r\n");
    }
    
    // last line: empty
    _requestHeader.append("\r\n");

    //cout << "http request is " << endl << _requestHeader << endl;
    //cout << "end of request" << endl;

    // return length
    return _requestHeader.length();
}

bool HttpSocket::_connect(char const *host)
{
    if(_status != HttpSocket::HTTP_SOCKET_UNINIT) return false;

    // create socket
    WSADATA tmp;
    if (WSAStartup(0x202, &tmp) == SOCKET_ERROR)
    {
        return false;
    }
    _socket = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(_socket == INVALID_SOCKET)
    {
        cerr << "socket error, code is: " << WSAGetLastError() << endl;
        return false;
    }

    if(host == NULL) return false;

    // get host name from ip address
    hostent *hostent = gethostbyname(host);
    if(hostent == NULL)
    {
        cerr << "gethostbyname error, code is: " << WSAGetLastError() << endl;
        return false;
    }

    // connect to server
    struct in_addr ip_addr;
    memcpy(&ip_addr, hostent->h_addr_list[0], 4);

    struct sockaddr_in destaddr;
    memset((void *)&destaddr, 0, sizeof(destaddr)); 
    destaddr.sin_family=AF_INET;
    destaddr.sin_port=htons(80);
    destaddr.sin_addr=ip_addr;

    char chIpAdd[256];
    sprintf_s(chIpAdd,"%d.%d.%d.%d",
        destaddr.sin_addr.S_un.S_un_b.s_b1,
        destaddr.sin_addr.S_un.S_un_b.s_b2,
        destaddr.sin_addr.S_un.S_un_b.s_b3,
        destaddr.sin_addr.S_un.S_un_b.s_b4);
    cout << "server ip is " << chIpAdd << endl;

    if(::connect(_socket,(struct sockaddr*)&destaddr,sizeof(destaddr)) != 0)
    {
        cerr << "connect error, code is: " << WSAGetLastError() << endl;
        return false;
    }

    int ret = setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&_timeOut, sizeof(_timeOut)); 
    if(ret != 0)
    {
        cerr << "setsockopt error, code is: " << WSAGetLastError() << endl;
        return false;
    }

    _status = HttpSocket::HTTP_SOCKET_CONNECTED;
    return true;
}

bool HttpSocket::_sendRequest()
{
    if(_status != HttpSocket::HTTP_SOCKET_CONNECTED) return false;

    if(send(_socket, _requestHeader.c_str(), (int)_requestHeader.length(), 0) == SOCKET_ERROR)
    {
        return false;
    }
    _retrieveResponseHeader();
    return true;
}

bool HttpSocket::_retrieveResponseHeader()
{
    char c;
    int i=0;
    while(true)
    {
        int ret = ::recv(_socket, &c, 1, 0);
        if (ret == SOCKET_ERROR)
        {
            cerr << "recv error, code is: " << WSAGetLastError() << endl;
            return false;
        }
        else if (ret == 0)
        {
            cerr << "server closed when process recv"<< endl;
            return false;
        }
        char tmp[2] = {c, '\0'};
        string temp(tmp);
        _responseHeader.append(temp);
        if(i >= 3 && _responseHeader[i] == '\n' && _responseHeader[i-2] == '\n' && \
            _responseHeader[i-1] == '\r' && _responseHeader[i-3] == '\r')
            break;
        i++;
    }

    _status = HttpSocket::HTTP_SOCKET_RESPONSED;
    //cout << "response is " << endl << _responseHeader << endl;
    //cout << "end of response" << endl;
    return true;
}

bool HttpSocket::_closeSocket()
{
    if(_socket != NULL && closesocket(_socket) == SOCKET_ERROR)
    {
        WSACleanup();
        return false;
    }
    WSACleanup();
    _socket = NULL;
    return true;
}

bool HttpSocket::getContent(string &content) const
{
    if (_status != HttpSocket::HTTP_SOCKET_RESPONSED) return false;

    content.clear();
    string value;
    if (getResponseField("Content-Length", value) != -1)
    {
        int size = atoi(value.c_str());
        return _recv(size, content);
    }
    // for chunked data
    else 
    {
        while(true)
        {
            char c;
            string hex;
            int i=0;
            while(true)
            {
                int ret = ::recv(_socket, &c, 1, 0);
                char tmp[2] = {c, '\0'};
                string temp(tmp);
                hex.append(temp);
                if(i >= 1 && hex[i] == '\n' && hex[i-1] == '\r')
                {
                    break;
                }
                i++;
            }
            int size = 0;
            sscanf_s(hex.c_str(), "%X", &size);
            if (size != 0)
            {
                _recv(size, content);
                char c;
                ::recv(_socket, &c, 1, 0); //0d
                ::recv(_socket, &c, 1, 0); //0a
            }
            else
            {
                return true;
            }
        }
    }
}

bool HttpSocket::_recv(int size, string &content) const
{
    //cout << "size is " << size << endl;
    int completed = 0;
    int const BLOCK_SIZE = 1024;
    char data[BLOCK_SIZE];
    while(completed < size)
    {
        int left = (BLOCK_SIZE < size - completed) ? BLOCK_SIZE : size - completed;
        //cout << "left is " << left << endl;
        int rece = recv(_socket, data, left, 0);
        if(rece == 0)
        {
            cerr << "server already closed" << endl;
            return false;
        }
        if(rece == -1)
        {
            cerr << "receive data over time" << endl;
            return false;
        }
        content.append(data, rece);
        completed += rece;
        //cout << "completed is " << completed << endl;
    } 
    return true;
}

string HttpSocket::getRequestHeader() const
{
    if (_status == HttpSocket::HTTP_SOCKET_RESPONSED || _status == HttpSocket::HTTP_SOCKET_GOT_CONTENT)
        return _requestHeader;
    else
        return "";
}

string HttpSocket::getResponseHeader() const
{
    if (_status == HttpSocket::HTTP_SOCKET_RESPONSED || _status == HttpSocket::HTTP_SOCKET_GOT_CONTENT)
        return _responseHeader;
    else
        return "";
}

int HttpSocket::getResponseField(string const &session, string &value) const
{
    if(_status != HttpSocket::HTTP_SOCKET_RESPONSED && _status != HttpSocket::HTTP_SOCKET_GOT_CONTENT) 
        return -1;
    
    size_t pos = _responseHeader.find(session, 0);
    if(pos != string::npos)
    {
        value.clear();
        pos += session.length();
        pos += 2;
        size_t cur = _responseHeader.find("\r\n", pos);
        value = _responseHeader.substr(pos, cur - pos);
        return (int)(cur - pos);
    }
    else
    {
        return -1;
    }
}

int HttpSocket::getServerState() const
{
    if(_status != HttpSocket::HTTP_SOCKET_RESPONSED && _status != HttpSocket::HTTP_SOCKET_GOT_CONTENT) 
        return -1;
    
    char state[3];
    state[0] = _responseHeader[9];
    state[1] = _responseHeader[10];
    state[2] = _responseHeader[11];

    return atoi(state);
}

HttpSocket::Status HttpSocket::getState() const
{
    return _status;
}